<?php
/**
 * Class <api>
 *
 * @package Temply-Account\Core
 * @license GPL-2.0
 * @author Sergey Dilong
 */

/**
 * Controls output flow, error messages displaying
 *
 * Basically it's used as a pallet of functions for api.php to don't clutter up global namespace
 *
 * @package Temply-Account\Core
 * @license GPL-2.0
 * @author Sergey Dilong
 *
 */
class api
{
    /**
     * Checks if an array has all required fields
     *
     * By default checks $secure array, but you mustn't use it directly from here
     * <code>
     *  self::check_required("field1, field2", ["field1" => 1, "field2" => 2])
     * </code>
     *
     * @param string $a Comma-separated (with spaces) required keys
     * @param array|string $b Array to be checked. If === 'NULL' - it checks $secure global
     *
     * @return bool True if all required params are presented, false - if not.
     */
    private static function check_required($a, $b = 'NULL')
    {
        global $secure;
        // Selecting an array for check
        if ($b === 'NULL') {
            $t = $secure;
        } else if (!is_array($b)) {
            return false;
        } else {
            $t = $b;
        }

        // Exploding req. params
        $r = explode(", ", $a);
        // Checking each
        foreach ($r as $k) {
            // Catching unfilled param
            if (!empty($k) && !isset($t[$k])) {return $k;}
        }
        return true;
    }

    /**
     *  Public method for check_required
     *
     * A mirror for check_required {@see api::check_required() Logic of initial function}, but instead of false it throws exception
     *
     * @param string $a Comma-separated (with spaces) required keys
     * @param array|string $b Array to be checked. If === 'NULL' - it checks $secure global
     *
     * @return bool True if everything is OK
     * @throws apiException [102] If there's no one or more required keys
     * @example "methods/Test/Server.php" 57 5 Fields could be required by statements
     *
     */
    public static function required($a, $b = 'NULL')
    {
        global $secure;
        // Checking an array
        $r = self::check_required($a, $b);
        // If found smthing - throw an error with parameter
        if ($r !== true) {
            throw new apiException(102, ["error_field" => $r]);
        }

        return true;
    }

    /**
     * Loads error from error library
     *
     * Gets error from ```/classes/help/errors```
     * Finds error info by code. First number - error section, others - error number.
     * If there's no such code it fallbacks to error 100.
     * If you would like to throw an error use {@see apiException Custom Exception class} or {@see api::error() a class method}
     *
     *
     * @param int $l Code number
     * @param mixed[] $o Array, which will be returned as ```extended``` key with the error
     *
     * @return array Error array to display
     *
     * @see apiException [Recommended] Throwing Exceptions
     * @see api::error() Setting errors
     */
    public static function get_error($l, $o = [])
    {
        $l = strval($l); // Stringify
        // If code < 1000
        if (strlen($l) == 3) {
            $m = $l[0]; // Get first number
            $c = intval(substr($l, 1)); // Get error number
        } else {
            $m = $l[0].$l[1]; // Get first two
            $c = intval(substr($l, 2)); // Get error number
        }

        $d = @json_decode(@file_get_contents("inc/classes/help/errors/$m.json"), true); // Get error list
        if (json_last_error() !== JSON_ERROR_NONE || !isset($d["errors"][$c])) { // If not found - fallback to error 100
            $m1 = 1;
            $c1 = 0;
        } else { // Else use 'as is'
            $m1 = $m;
            $c1 = $c;
        }

        if ($m !== $m1) { // If fallback was used - get new error
            $d = @json_decode(@file_get_contents("inc/classes/help/errors/$m1.json"), true);
        }

        if (json_last_error() !== JSON_ERROR_NONE || !isset($d["errors"][$c])) {
            // If fallbak failed - use this value
            $rs = [
                "info" => "Undefined error",
            ];
        } else {
            $rs = $d["errors"][$c];
        }

        // Record the error to output
        $re = $rs;
        // Make some beautify-changes
        $re['error_code'] = intval($m . ($c > 9 ? $c : "0" . $c));
        // Give the user more data if we can
        if (count($o) > 0) {
            $re["extended"] = $o;
        }

        // Give the error
        return $re;
    }

    /**
     * Get and terminate execution of API method with an error
     *
     * If you would like to throw any errors I strongly recommend you to use {@see apiException Custom Exception class}
     *
     * @see api::get_error() Method, which generates the error
     *
     * @param int $l Code number
     * @param mixed[] $o Array, which will be returned as ```extended``` key with the error
     *
     * @return void It just terminates the script
     */
    public static function error($l, $o = [])
    {
        global $secure, $report_enabled;

        try {
            $re = self::get_error($l, $o);

            // Error data. CURRENT_METHOD will be returned always
            $re["data"] = [
                "method" => CURRENT_METHOD,
            ];
            // If we can return params - return it
            if (count($secure) > 0) {
                $re["data"]["params"] = $secure;
            }

            // If we should record a report - record it
            if ($report_enabled) {
                // Calling Report class for dat
                $l = new Report(json_encode($re, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
                // Get the report ID for output
                $ert = $l->getID();
                if ($ert) {
                    $re["data"]["report_id"] = $ert;
                }

            }

            // Remove trash
            unset($re['code']);

            // Echo the error and die
            if (RESPONSE_TYPE == "XML") {
                echo HelpClasses\ArrayToXml::convert($re);
            } else {
                echo json_encode($re, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            }

            die();
        } catch (Exception $e) {
            die('{"code": 0, "info": "We have some troubles right now"}');
        }
    }

    /**
     * Define preferred data type for client
     *
     * This method decides which encoding could be accepted by client.
     * For now supported types are *JSON* and *XML* (but XML doesn't supported by fatal errors)
     *
     * @return void
     */
    public static function getInputData()
    {
        if (CONTENT_TYPE === 'application/json') { // If JSON was requested
            self::DataAsJSON();
        } else
        if (CONTENT_TYPE === 'application/xml') { // If XML was requested
            self::DataAsXML();
        } else {
            // Output Content Type
            header('Content-Type: application/json');
        }
    }

    /**
     * Decodes and merges input data if in POST-Raw JSON-encoded data was detected
     *
     * Works with global variables, sets ```Content-Type: application/json``` header
     *
     * @return void
     */
    private static function DataAsJSON()
    {
        $content = file_get_contents("php://input");
        $decoded = json_decode($content, true);
        if (!is_array($decoded)) {
            return;
        }

        $_POST = array_merge($_POST, $decoded);
        /** Response type */
        define("RESPONSE_TYPE", "JSON");
        // Output Content Type
        header('Content-Type: application/json');
    }

    /**
     * Decodes and merges input data if in POST-Raw XML-encoded data was detected
     *
     * Works with global variables, sets ```Content-Type: application/xml``` header
     *
     * @return void
     */
    private static function DataAsXML()
    {
        $xmlstring = file_get_contents("php://input");
        $xml = simplexml_load_string($xmlstring, "SimpleXMLElement", LIBXML_NOCDATA);
        $json = json_encode($xml);
        $array = json_decode($json, true);
        if (is_array($array)) {
            $_POST = array_merge($_POST, $array);
        }

        /** @ignore */
        define("RESPONSE_TYPE", "XML");
        // Output Content Type
        header('Content-Type: application/xml');
    }
}